package org.spring.bean.factory.xml;

import cn.hutool.core.util.StrUtil;
import org.spring.bean.MutablePropertyValues;
import org.spring.bean.PropertyValue;
import org.spring.bean.exceptions.BeanDefinitionStoreException;
import org.spring.bean.factory.config.BeanDefinition;
import org.spring.bean.factory.config.RuntimeBeanNameReference;
import org.spring.bean.factory.support.RootBeanDefinition;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;

import static org.spring.bean.factory.xml.BeanDefinitionParserDelegate.*;

/**
 * {@link BeanDefinitionDocumentReader}接口的默认实现，该接口根据“Spring -beans”DTD和XSD格式(Spring的默认XML bean定义格式)读取bean定义。
 *
 * <p>所需XML文档的结构、元素和属性名称都硬编码在这个类中。(当然，如果需要生成这种格式，可以运行转换)。
 * {@code <beans>}不需要是XML文档的根元素:这个类将解析XML文件中的所有bean定义元素，而不管实际的根元素是什么。
 *
 * @author 李坤
 * @date 2022/4/23 14:44
 */
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

    @Override
    public void registerBeanDefinitions(Document document, XmlReaderContext xmlReaderContext) throws BeanDefinitionStoreException {
        Node node;
        final NodeList childNodes = document.getDocumentElement().getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
            if ((node = childNodes.item(i)) instanceof Element) {
                if (BEAN_ELEMENT.equals(node.getNodeName())) {
                    parseBeanElement2registerBeanDefinition((Element) node, xmlReaderContext);
                }
            }
        }
    }

    /**
     * 解析 bean 元素，并将其转换为 {@link BeanDefinition} 对象。
     *
     * @param beanElement      bean 元素
     * @param xmlReaderContext 当前的 {@link XmlReaderContext}
     */
    private void parseBeanElement2registerBeanDefinition(Element beanElement, XmlReaderContext xmlReaderContext) {
        String beanName;
        RootBeanDefinition rootBeanDefinition;

        // 解析/获取 bean 名称
        if (StrUtil.isEmpty(beanName = beanElement.getAttribute(ID_ATTRIBUTE))) {
            beanName = beanElement.getAttribute(NAME_ATTRIBUTE);
        }
        if (StrUtil.isEmpty(beanName)) {
            throw new BeanDefinitionStoreException("bean 元素必须包含 id 属性或 name 属性");
        } else {
            // 解析 cla所属 属性，并创建 BeanDefinition 对象
            String classAttribute;

            if (StrUtil.isEmpty(classAttribute = beanElement.getAttribute(CLASS_ATTRIBUTE))) {
                throw new BeanDefinitionStoreException("bean 元素必须包含 class 属性");
            }
            rootBeanDefinition = new RootBeanDefinition(classAttribute);
        }
        parsePropertyElements2registerBeanDefinition(beanElement.getChildNodes(), rootBeanDefinition);
        // 注册 BeanDefinition 对象
        xmlReaderContext.getXmlBeanDefinitionReader().getBeanDefinitionRegistry().registerBeanDefinition(beanName, rootBeanDefinition);
    }

    /**
     * 解析 bean 元素中的子元素，并将其填充到 {@link BeanDefinition} 对象中。
     *
     * @param propertyElementList bean 元素的子元素
     * @param rootBeanDefinition  当前的 {@link BeanDefinition}
     */
    private void parsePropertyElements2registerBeanDefinition(NodeList propertyElementList, RootBeanDefinition rootBeanDefinition) {
        Node node;
        final int length = propertyElementList.getLength();
        List<PropertyValue> propertyValueList = new ArrayList<>(length);

        for (int index = 0; index < length; index++) {
            if ((node = propertyElementList.item(index)) instanceof Element) {
                if (PROPERTY_ELEMENT.equals(node.getNodeName())) {
                    propertyValueList.add(parsePropertyElement2PropertyValue((Element) node));
                }
            }
        }
        rootBeanDefinition.setPropertyValues(new MutablePropertyValues(propertyValueList));
    }

    /**
     * 解析 property 元素，并将其转换为 {@link PropertyValue} 对象。
     *
     * @param propertyElement property 元素
     *
     * @return {@link PropertyValue} 对象
     */
    private PropertyValue parsePropertyElement2PropertyValue(Element propertyElement) {
        String valueAttribute;
        final String nameAttribute;
        PropertyValue propertyValue;

        if (StrUtil.isEmpty(nameAttribute = propertyElement.getAttribute(NAME_ATTRIBUTE))) {
            throw new BeanDefinitionStoreException("property 元素必须包含 name 属性");
        }
        if (StrUtil.isNotEmpty(valueAttribute = propertyElement.getAttribute(VALUE_ATTRIBUTE))) {
            propertyValue = new PropertyValue(nameAttribute, valueAttribute);
        } else {
            String refAttribute;
            if (StrUtil.isEmpty(refAttribute = propertyElement.getAttribute(REF_ATTRIBUTE))) {
                throw new BeanDefinitionStoreException("property 元素必须包含 ref 或 value 属性");
            }
            propertyValue = new PropertyValue(nameAttribute, new RuntimeBeanNameReference(refAttribute));
        }
        return propertyValue;
    }

}
